/*
 *  NumberValidator.cs
 *  
 *  Validates whether the user input is a valid number.
 *  
 *  Copyright (c) 2009, Richard Bao Labs.
 *  
 *  The contents of this file are subject to the Mozilla Public License
 *  Version 1.1 (the "License"); you may not use this file except in
 *  compliance with the License. You may obtain a copy of the License at
 *  
 *  http://www.mozilla.org/MPL/
 *  
 *  Software distributed under the License is distributed on an "AS IS"
 *  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 *  the License for the specific language governing rights and limitations
 *  under the License.
 *  
 *  The Original Code is RichControlLibrary.
 *  
 *  The Initial Developer of the Original Code is Richard Bao Labs.
 *  All Rights Reserved.
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace RichControlLibrary
{
    /// <summary>
    /// Validates whether the user input is a valid number.
    /// </summary>
    /// <remarks>
    /// This validator accepts general number format, including positive/negative signs, commas (as 
    /// thousands separators), and point.
    /// <example>
    /// Valid:<br />
    /// 123<br />
    /// 12.5<br />
    /// -12345.23234<br />
    /// 1,234,567,890.12<br />
    /// </example>
    /// </remarks>
    public class NumberValidator : RegexValidator
    {
        /// <summary>
        /// Returns a <see cref="Regex"/> instance as the validating regular expression.
        /// </summary>
        /// <returns>A <see cref="Regex"/> used to validate whether the user input is a valid 
        /// number.</returns>
        protected override Regex GetValidatingRegex()
        {
            string decimalPoint = Regex.Escape(_decimalSeparatorSymbol);
            string thousandSeparator = Regex.Escape(_thousandsSeparatorSymbol);

            StringBuilder regexStr = new StringBuilder();

            regexStr.Append(@"^");
            regexStr.Append(@"(?<sign>[+-]?)");

            if (_allowsThousandsSeparator)
            {
                regexStr.Append(@"(");
                regexStr.Append(@"(?<groupednum>[0-9]{0,3}(" + thousandSeparator + "[0-9]{3})*)");
                regexStr.Append(@"|");
            }

            regexStr.Append(@"(?<num>[0-9]*)");

            if (_allowsThousandsSeparator)
            {
                regexStr.Append(@")");
            }

            if (_allowsDecimalFraction)
            {
                regexStr.Append(@"(?<decimal>" + decimalPoint + "[0-9]+)?");
            }

            regexStr.Append(@"$");

            return new Regex(regexStr.ToString());
        }

        private bool _allowsDecimalFraction = true;
        /// <summary>
        /// Gets or sets a <see cref="System.Boolean"/> value that indicates whether the decimal fraction
        /// part is acceptable.
        /// </summary>
        public bool AllowsDecimalFraction
        {
            get { return _allowsDecimalFraction; }
            set { _allowsDecimalFraction = value; }
        }

        private bool _allowsThousandsSeparator = true;
        /// <summary>
        /// Gets or sets a <see cref="System.Boolean"/> value that indicates whether the thousands
        /// separator is acceptable.
        /// </summary>
        public bool AllowsThousandsSeparator
        {
            get { return _allowsThousandsSeparator; }
            set { _allowsThousandsSeparator = value; }
        }

        private string _decimalSeparatorSymbol = ".";
        /// <summary>
        /// Gets or sets the decimal separator symbol. It should be dot "." in most culture, or comma ","
        /// in some culture (such as France).
        /// </summary>
        public string DecimalSeparatorSymbol
        {
            get { return _decimalSeparatorSymbol; }
            set { _decimalSeparatorSymbol = value; }
        }

        private string _thousandsSeparatorSymbol = ",";
        /// <summary>
        /// Gets or sets the thousands separator symbol. It should be comma "," in most culture, or dot "."
        /// in some culture (such as France).
        /// </summary>
        public string ThousandsSeparatorSymbol
        {
            get { return _thousandsSeparatorSymbol; }
            set { _thousandsSeparatorSymbol = value; }
        }

        private bool _autoOmitPositiveSign = true;
        /// <summary>
        /// Gets or sets a <see cref="System.Boolean"/> value that indicates whether to omit the positive
        /// sign automatically.
        /// </summary>
        public bool AutoOmitPositiveSign
        {
            get { return _autoOmitPositiveSign; }
            set { _autoOmitPositiveSign = value; }
        }

        private bool _autoGroupByThousands = true;
        /// <summary>
        /// Gets or sets a <see cref="System.Boolean"/> value that indicates whether to group by thousands
        /// automatically.
        /// </summary>
        public bool AutoGroupByThousands
        {
            get { return _autoGroupByThousands; }
            set { _autoGroupByThousands = value; }
        }

        /// <summary>
        /// Gets a <see cref="System.Boolean"/> value that indicates whether the auto formatting feature 
        /// is supported by this validator.
        /// </summary>
        public override bool SupportsAutoFormatting
        {
            get
            {
                return _autoOmitPositiveSign || _autoGroupByThousands;
            }
        }

        /// <summary>
        /// Gets the auto formatted text if this validator supports auto correction feature.
        /// </summary>
        /// <param name="rawText">The raw text string entered by user.</param>
        /// <param name="match">The matching result of the validating regular expression.</param>
        /// <returns>The auto formatted text.</returns>
        protected override string GetAutoFormattedText(string rawText, Match match)
        {
            // Get parts

            string sign = (match.Groups["sign"].Success ? match.Groups["sign"].Value : null);

            string groupednum = null;
            string num = null;

            if (match.Groups["groupednum"].Success)
            {
                groupednum = match.Groups["groupednum"].Value;
            }

            if (match.Groups["num"].Success)
            {
                num = match.Groups["num"].Value;
            }

            string dec = (match.Groups["decimal"].Success ? match.Groups["decimal"].Value : null);

            // Auto format

            StringBuilder sb = new StringBuilder();

            if (!(_autoOmitPositiveSign && sign == "+"))
            {
                sb.Append(sign);
            }

            if (_autoGroupByThousands && groupednum == null)
            {
                StringBuilder grouping = new StringBuilder();
                for (int i = 0; i < num.Length; i++)
                {
                    grouping.Append(num[i]);
                    if ((num.Length - i) % 3 == 1 && i != num.Length - 1)
                    {
                        grouping.Append(_thousandsSeparatorSymbol);
                    }
                }
                sb.Append(grouping.ToString());
            }
            else
            {
                sb.Append(groupednum);
            }

            if (dec != null)
            {
                sb.Append(dec);
            }

            return sb.ToString();
        }
    }
}
